// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ast

import (
	"go/token"
	"sort"
)

// ----------------------------------------------------------------------------
// 导出筛选

// exportFilter是一个特殊的筛选函数，用于提取导出的节点。
func exportFilter(name string) bool {
	return IsExported(name)
}

// FileExports为Go源文件修剪AST，以便
// 仅保留导出的节点：所有未导出的顶级标识符
// 及其相关信息（如类型、初始值或函数
// 主体）已移除。导出类型的非导出字段和方法为
// 脱光衣服。文件.Comments列表未更改。
// None
// FileExports报告是否存在导出的声明。
// None
func FileExports(src *File) bool {
	return filterFile(src, exportFilter, true)
}

// PackageExports为Go软件包修剪AST，以便
// 只保留导出的节点。pkg.Files列表未更改，因此
// 文件名和顶级包注释不会丢失。
// None
// PackageExports报告是否存在导出声明；
// 否则返回false。
// None
func PackageExports(pkg *Package) bool {
	return filterPackage(pkg, exportFilter, true)
}

// ----------------------------------------------------------------------------
// 一般滤波

type Filter func(string) bool

func filterIdentList(list []*Ident, f Filter) []*Ident {
	j := 0
	for _, x := range list {
		if f(x.Name) {
			list[j] = x
			j++
		}
	}
	return list[0:j]
}

// fieldName假定x是匿名字段的类型，并且
// 返回相应的字段名。如果x是不可接受的
// 匿名字段，结果为零。
// None
func fieldName(x Expr) *Ident {
	switch t := x.(type) {
	case *Ident:
		return t
	case *SelectorExpr:
		if _, ok := t.X.(*Ident); ok {
			return t.Sel
		}
	case *StarExpr:
		return fieldName(t.X)
	}
	return nil
}

func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) {
	if fields == nil {
		return false
	}
	list := fields.List
	j := 0
	for _, f := range list {
		keepField := false
		if len(f.Names) == 0 {
			// 匿名字段
			name := fieldName(f.Type)
			keepField = name != nil && filter(name.Name)
		} else {
			n := len(f.Names)
			f.Names = filterIdentList(f.Names, filter)
			if len(f.Names) < n {
				removedFields = true
			}
			keepField = len(f.Names) > 0
		}
		if keepField {
			if export {
				filterType(f.Type, filter, export)
			}
			list[j] = f
			j++
		}
	}
	if j < len(list) {
		removedFields = true
	}
	fields.List = list[0:j]
	return
}

func filterCompositeLit(lit *CompositeLit, filter Filter, export bool) {
	n := len(lit.Elts)
	lit.Elts = filterExprList(lit.Elts, filter, export)
	if len(lit.Elts) < n {
		lit.Incomplete = true
	}
}

func filterExprList(list []Expr, filter Filter, export bool) []Expr {
	j := 0
	for _, exp := range list {
		switch x := exp.(type) {
		case *CompositeLit:
			filterCompositeLit(x, filter, export)
		case *KeyValueExpr:
			if x, ok := x.Key.(*Ident); ok && !filter(x.Name) {
				continue
			}
			if x, ok := x.Value.(*CompositeLit); ok {
				filterCompositeLit(x, filter, export)
			}
		}
		list[j] = exp
		j++
	}
	return list[0:j]
}

func filterParamList(fields *FieldList, filter Filter, export bool) bool {
	if fields == nil {
		return false
	}
	var b bool
	for _, f := range fields.List {
		if filterType(f.Type, filter, export) {
			b = true
		}
	}
	return b
}

func filterType(typ Expr, f Filter, export bool) bool {
	switch t := typ.(type) {
	case *Ident:
		return f(t.Name)
	case *ParenExpr:
		return filterType(t.X, f, export)
	case *ArrayType:
		return filterType(t.Elt, f, export)
	case *StructType:
		if filterFieldList(t.Fields, f, export) {
			t.Incomplete = true
		}
		return len(t.Fields.List) > 0
	case *FuncType:
		b1 := filterParamList(t.Params, f, export)
		b2 := filterParamList(t.Results, f, export)
		return b1 || b2
	case *InterfaceType:
		if filterFieldList(t.Methods, f, export) {
			t.Incomplete = true
		}
		return len(t.Methods.List) > 0
	case *MapType:
		b1 := filterType(t.Key, f, export)
		b2 := filterType(t.Value, f, export)
		return b1 || b2
	case *ChanType:
		return filterType(t.Value, f, export)
	}
	return false
}

func filterSpec(spec Spec, f Filter, export bool) bool {
	switch s := spec.(type) {
	case *ValueSpec:
		s.Names = filterIdentList(s.Names, f)
		s.Values = filterExprList(s.Values, f, export)
		if len(s.Names) > 0 {
			if export {
				filterType(s.Type, f, export)
			}
			return true
		}
	case *TypeSpec:
		if f(s.Name.Name) {
			if export {
				filterType(s.Type, f, export)
			}
			return true
		}
		if !export {
			// 对于常规筛选（不仅仅是导出），
			// 过滤器类型，即使名称未被过滤
			// 出来
			// 如果类型包含筛选的元素，
			// 遵守申报单。
			return filterType(s.Type, f, export)
		}
	}
	return false
}

func filterSpecList(list []Spec, f Filter, export bool) []Spec {
	j := 0
	for _, s := range list {
		if filterSpec(s, f, export) {
			list[j] = s
			j++
		}
	}
	return list[0:j]
}

// FilterDecl通过删除
// 所有名称（包括结构字段和接口方法名称，但
// 不是从参数列表中）不通过过滤器f。
// None
// FilterDecl报告是否有任何声明的名称在
// 过滤。
// None
func FilterDecl(decl Decl, f Filter) bool {
	return filterDecl(decl, f, false)
}

func filterDecl(decl Decl, f Filter, export bool) bool {
	switch d := decl.(type) {
	case *GenDecl:
		d.Specs = filterSpecList(d.Specs, f, export)
		return len(d.Specs) > 0
	case *FuncDecl:
		return f(d.Name.Name)
	}
	return false
}

// FilterFile通过删除所有文件来修剪Go文件的AST
// 顶级声明中的名称（包括结构字段和
// 接口方法名称（但不是来自参数列表），但这些名称
// 通过过滤器f。如果声明之后为空，
// 该声明将从AST中删除。进口报关单
// 总是被移除。文件.Comments列表未更改。
// None
// FilterFile报告是否存在任何顶级声明
// 过滤后留下。
// None
func FilterFile(src *File, f Filter) bool {
	return filterFile(src, f, false)
}

func filterFile(src *File, f Filter, export bool) bool {
	j := 0
	for _, d := range src.Decls {
		if filterDecl(d, f, export) {
			src.Decls[j] = d
			j++
		}
	}
	src.Decls = src.Decls[0:j]
	return j > 0
}

// FilterPackage通过移除组件将AST修剪到位，使其成为Go包
// 顶级声明中的所有名称（包括结构字段和
// 接口方法名称（但不是来自参数列表），但这些名称
// 通过过滤器f。如果声明之后为空，
// 该声明将从AST中删除。pkg.Files列表不可用
// 已更改，因此不会获取文件名和顶级包注释
// 迷路的
// None
// FilterPackage报告是否存在任何顶级声明
// 过滤后留下。
// None
func FilterPackage(pkg *Package, f Filter) bool {
	return filterPackage(pkg, f, false)
}

func filterPackage(pkg *Package, f Filter, export bool) bool {
	hasDecls := false
	for _, src := range pkg.Files {
		if filterFile(src, f, export) {
			hasDecls = true
		}
	}
	return hasDecls
}

// ----------------------------------------------------------------------------
// 包文件的合并

// MergeMode标志控制MergePackageFiles的行为。
type MergeMode uint

const (
	// 如果设置，则排除重复的函数声明。
	FilterFuncDuplicates MergeMode = 1 << iota
	// 如果已设置，则为与特定对象不关联的注释
	// 不包括AST节点（如文档或注释）。
	FilterUnassociatedComments
	// 如果设置，则排除重复的导入声明。
	FilterImportDuplicates
)

// nameOf返回函数名（foo）或方法名（foo.bar）
// 给定的函数声明。如果AST不适用于
// 在接收端，它承担一个函数。
// None
func nameOf(f *FuncDecl) string {
	if r := f.Recv; r != nil && len(r.List) == 1 {
		// 看起来是正确的接收者声明
		t := r.List[0].Type
		// 取消引用指针接收器类型
		if p, _ := t.(*StarExpr); p != nil {
			t = p.X
		}
		// 接收方类型必须是类型名称
		if p, _ := t.(*Ident); p != nil {
			return p.Name + "." + f.Name.Name
		}
		// 否则，假设一个函数
	}
	return f.Name.Name
}

// 分隔符是空的
// 将不同的注释组连接到单个组时
// None
var separator = &Comment{token.NoPos, "// "}

// MergePackageFiles通过合并
// 属于包的文件。模式标志控制合并行为。
// None
func MergePackageFiles(pkg *Package, mode MergeMode) *File {
	// 计算整个系统中包文档、注释和声明的数量
	// 所有包文件。另外，计算文件名的排序列表，以便
	// 后续迭代始终可以以相同的顺序进行迭代。
	ndocs := 0
	ncomments := 0
	ndecls := 0
	filenames := make([]string, len(pkg.Files))
	i := 0
	for filename, f := range pkg.Files {
		filenames[i] = filename
		i++
		if f.Doc != nil {
			ndocs += len(f.Doc.List) + 1 // +1用于分离器
		}
		ncomments += len(f.Comments)
		ndecls += len(f.Decls)
	}
	sort.Strings(filenames)

	// 将所有包文件中的包注释收集到单个
	// CommentGroup—收集的软件包文档。一般来说
	// 只有一个文件带有包注释；但它是
	// 收集额外的评论比把它们扔在地板上要好。
	var doc *CommentGroup
	var pos token.Pos
	if ndocs > 0 {
		list := make([]*Comment, ndocs-1) // -1：第一组前没有分隔符
		i := 0
		for _, filename := range filenames {
			f := pkg.Files[filename]
			if f.Doc != nil {
				if i > 0 {
					// 不是第一组-添加分隔符
					list[i] = separator
					i++
				}
				for _, c := range f.Doc.List {
					list[i] = c
					i++
				}
				if f.Package > pos {
					// 将最大包装条款位置保持为
					// 合并文件包子句的位置
					// 文件夹。
					pos = f.Package
				}
			}
		}
		doc = &CommentGroup{list}
	}

	// 从所有包文件收集声明。
	var decls []Decl
	if ndecls > 0 {
		decls = make([]Decl, ndecls)
		funcs := make(map[string]int) // func名称映射->decls索引
		i := 0                        // 当前索引
		n := 0                        // 已筛选条目的数目
		for _, filename := range filenames {
			f := pkg.Files[filename]
			for _, d := range f.Decls {
				if mode&FilterFuncDuplicates != 0 {
					// 语言实体可以声明为多个
					// 不同包文件中的时间；只在
					// 构建时声明必须是唯一的。
					// 现在，排除多个
					// 功能-保留带有文档的功能。
					// None
					// TODO（gri）：将此筛选扩展到其他
					// 实体（常量、类型、变量）
					// 多个声明是常见的。
					if f, isFun := d.(*FuncDecl); isFun {
						name := nameOf(f)
						if j, exists := funcs[name]; exists {
							// 函数已声明
							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
								// 现有声明没有文件；
								// 忽略现有的声明
								decls[j] = nil
							} else {
								// 忽略新的声明
								d = nil
							}
							n++ // 筛选条目
						} else {
							funcs[name] = i
						}
					}
				}
				decls[i] = d
				i++
			}
		}

		// 如果条目被删除，则从decls列表中删除nil条目
		// 过滤。为了避免干扰，我们使用了第二遍
		// 源中的原始声明顺序（否则，此
		// 也会使单调递增的位置无效
		// 单个文件中的信息）。
		if n > 0 {
			i = 0
			for _, d := range decls {
				if d != nil {
					decls[i] = d
					i++
				}
			}
			decls = decls[0:i]
		}
	}

	// 从所有包文件中收集导入规范。
	var imports []*ImportSpec
	if mode&FilterImportDuplicates != 0 {
		seen := make(map[string]bool)
		for _, filename := range filenames {
			f := pkg.Files[filename]
			for _, imp := range f.Imports {
				if path := imp.Path.Value; !seen[path] {
					// 待办事项：考虑处理以下情况：
					// -存在两个具有相同导入路径的导入，但
					// 具有不同的本地名称（可能应该
					// 保留两个）
					// -存在2个导入，但只有一个有注释
					// -存在2个进口，并且它们都有（可能
					// （不同）评论
					imports = append(imports, imp)
					seen[path] = true
				}
			}
		}
	} else {
		// 迭代文件名以确定顺序。
		for _, filename := range filenames {
			f := pkg.Files[filename]
			imports = append(imports, f.Imports...)
		}
	}

	// 从所有包文件中收集注释。
	var comments []*CommentGroup
	if mode&FilterUnassociatedComments == 0 {
		comments = make([]*CommentGroup, ncomments)
		i := 0
		for _, filename := range filenames {
			f := pkg.Files[filename]
			i += copy(comments[i:], f.Comments)
		}
	}

	// TODO（gri）需要计算未解析的标识符！
	return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
}
